iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 23
0
自我挑戰組

非本科之30天Ruby / Rails學習筆記系列 第 23

Day23: Rails中的N+1問題

  • 分享至 

  • xImage
  •  

Rails要建立關聯非常簡單(one-to-one, one-to-many),但也是這個原因,造成在資料庫查詢的時候浪費許多記憶體,大部分的 ORM 預設使用 lazy-loading,一筆資料的查詢就會產生一筆 query,拖累了資料庫的效能。因此將預設的 lazy-loading 改成 Eager-loading 就可以解決N+1問題。

其中 Eager-loading在 rails 提供了4種方式:

  • preload
  • eagerload
  • includes
  • joins

這篇就拿最常見的inclundes及joins來討論。

我們先建立兩個Model分別為user.rbproduct.rb且為一對多關係:

#user.rb
class User < ApplicationRecord
  has_many :products
end

#product.rb
class Product < ApplicationRecord
  belongs_to :user
end

controller先將所有的Pdoduct撈出:

#products_controller.rb

def index
  @products = Product.all
end

再一筆一筆印出來:

<!-- index.erb.html -->
  <% @products.each do |product| %>
    <%= product.user.name%>
    <%= product.title%>
    <%= product.price%>
    ....
  <% end %>

乍看之下好像很合理,如果說只有一筆product要查這樣寫沒問題,但一個網站怎麼可能只有一筆資料?

若今天要找出product的擁有者、名字及價錢有10筆。在迭代每筆資料時,依邊呼叫 query 來從 Prdoduct 資料表中取資料,就會產生 10 + 1 次 query,其中後面的 1 指的是 User資料數量。

資料的存取是rails的弱項,所以在設計時能夠避免 N+1問題就避免,將資料存取相關的事交給擅長的資料庫就好。

includes

拿上面例子來看,會產生 10+1筆的資料存取,我們可以用includes的方式將所有資料在資料第一次存取時就“一次查完“因此在controller的部份我們可以這樣寫:

#products_controller.rb

def index
  @products = Product.includes(:user)
end

再 query 的部分就只會查詢2筆了。

joins與includes的差別

:joins 使用 SQL 的 INNER JOIN 方法,不會真的把關聯的資料取出來。如果只是想要篩選結果,或是觀察關聯物件的某些屬性質,那麼使用 :joins 是最有效率的。不過有一點要注意,如果你想要做的事是存取關聯物件本身,那麼 :joins 還是會造成 N+1 問題。

主要差別在於:

  1. join主要用於過濾model之間的關係,但對查詢筆數來說並無太大幫助
  2. include主要用於將大量資料在同一筆查詢內一次查好

參考資料:

部分內容擷取自以下連結
Preload, Eagerload, Includes and Joins
Rails使用include和join避免 N+1 queries
[Rails] N+1 Queries Problem
Rails API

“Tests are a gift. And great tests are a great gift. To fail the test is a misfortune. But to refuse the test is to refuse the gift, and something worse, more >irrevocable, than misfortune.”

– Lois McMaster Bujold, writer

本文同步發佈於: https://louiswuyj.tw/


上一篇
Day22: 簡易Rails實作(下)
下一篇
Day24: Rails中的find? find_by? where?
系列文
非本科之30天Ruby / Rails學習筆記30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言